import { HitRecord, type Hitable, type HitResult } from "./hitUtil"
import type { Attenuation, Material } from "./materialUtil"
import type { Ray } from "./rayUtil"
import { dot, Vec3 } from "./vectorUtil"

/**
 * 球体
 */
export class Sphere implements Hitable {

  id?: string

  /** 球心坐标 */
  center: Vec3

  /** 球体半径 */
  radius: number

  /** 材质 */
  material?: Material

  constructor(center: Vec3, r: number, material?: Material, id?: string) {
    this.center = center
    this.radius = r
    this.material = material
    this.id = id
  }

  /**
   * 光线交互函数，光线会产生变化，并返回结果
   * @param ray 
   * @param t_min 
   * @param t_max 
   * @returns 
   */
  hit(ray: Ray, t_min: number, t_max: number): HitResult | null {

    let hit = new HitRecord()
    hit.id = this.id

    // 向量推导，向量平方使用点乘
    // 发光点到球心OC的向量与光线到球的向量td的差值向量的平方为r²
    // (td-OC)² = r²
    // 点乘满足乘法运算，可以展开为二项式
    // d²t²-2OCdt+OC²-r² = 0
    // 光线来源到球心的向量
    const vOC = this.center.sub(ray.origin)
    // 计算系数 
    const a = dot(ray.direction, ray.direction)
    const b = dot(vOC, ray.direction) * (-2)
    const c = dot(vOC, vOC) - this.radius ** 2
    // 求根公式，为正值，则有交点，否则没有
    const discriminate = b ** 2 - 4 * a * c

    if (discriminate > 0) {
      // 得到双解，因为与球有前后两个焦点，取最近的
      const temp1 = (-b - Math.sqrt(discriminate)) / (2 * a)
      const temp2 = (-b + Math.sqrt(discriminate)) / (2 * a)
      const temp = Math.min(temp1, temp2)

      // 限定时间范围，不超过最大时间，大于最小时间
      if (temp > t_min && temp < t_max) {
        // 设置行进时间
        hit.time = temp
        // 设置沿方向行进的向量
        hit.point = ray.getPoint(temp)
        // 设置法线方向 中心到碰撞点
        hit.normal = hit.point.sub(this.center).unitVec()
        // 设置反射光线
        let _ray = ray.reflect(hit)
        // 设置衰减
        let attenuation: Attenuation = new Vec3(1, 1, 1)
        // 应用材质散射
        if (this.material) {
          [_ray, attenuation] = this.material.scatter(ray, hit)
        }

        return {
          hit,
          reflectRay: _ray,
          attenuation,
          isDiffusion: this.material?.isDiffuce
        } as HitResult
      }
    }
    return null
  }
}